﻿Imports System.Windows
Imports System.Collections.ObjectModel
Imports System.Windows.Media.Imaging
Imports System.Threading
Imports System
Imports System.IO
Imports System.ComponentModel
Imports System.Windows.Media
Imports System.Collections.Generic
Imports System.Windows.Documents
Imports System.Windows.Controls
Imports System.Windows.Threading

Public Class ComicPages
    Inherits ObservableCollection(Of comicPage)
End Class

Public Class comicPage
    Implements INotifyPropertyChanged

    Private BufferSize As Integer = 32768

    Private _loading As Boolean = False
    Private _largeImageLoading As Boolean = False
    Private _filename As String
    Private _imageSource As ImageSource
    Private _largeImageSource As ImageSource

    Private Delegate Sub NoArgumentDelegate(ByVal mem As MemoryStream)

    Private Shared _lockStack As New Object
    Private Shared _loadStack As New Stack(Of comicPage)
    Private Shared _hourGlass As BitmapImage
    'Private Shared _loaderThread As Thread
    Private Shared _loadImageEvent As New AutoResetEvent(False)
    Private Shared _lockLargeLoad As New Object
    Private Shared _largePageToLoad As comicPage = Nothing
    Private Shared _loadLargePhotoEvent As New AutoResetEvent(False)
    'Private Shared _largeLoaderThread As Thread

    Shared Sub New()
        _hourGlass = New BitmapImage(New Uri("pack://application:,,,/Resources/HourGlass.png"))
        '_loaderThread = New Thread(New ThreadStart(AddressOf LoaderThreadLoop))
        '_loaderThread.IsBackground = True ' otherwise, the app won't quit with the UI...
        '_loaderThread.Priority = ThreadPriority.BelowNormal
        '_loaderThread.Start()

        '_largeLoaderThread = New Thread(New ThreadStart(AddressOf LargeLoaderThreadLoop))
        '_largeLoaderThread.IsBackground = True ' otherwise, the app won't quit with the UI...
        '_largeLoaderThread.Priority = ThreadPriority.BelowNormal
        '_largeLoaderThread.Start()
    End Sub

    Private Shared Sub LargeLoaderThreadLoop()
        Do
            _loadLargePhotoEvent.WaitOne()
            Try
                Dim loadPage As comicPage = Nothing
                Lock(_lockLargeLoad)
                If Not _largePageToLoad Is Nothing Then
                    loadPage = _largePageToLoad
                    _largePageToLoad = Nothing
                End If
                Unlock(_lockLargeLoad)
                If Not loadPage Is Nothing Then
                    Try
                        Dim bi As New BitmapImage(loadPage.FilenameUri)
                        bi.Freeze()
                        loadPage.LargeImageSource = bi
                    Catch ex As Exception
                        System.Diagnostics.Debug.WriteLine(ex)
                    End Try
                End If
            Catch ex As Exception
                System.Diagnostics.Debug.WriteLine(ex)
            End Try
        Loop While True
    End Sub

    Private Shared Sub LoaderThreadLoop()
        Do
            _loadImageEvent.WaitOne()
            Try
                While (ProcessLoadImageStack(Nothing))
                    Thread.Sleep(10)
                End While
            Catch ex As Exception
                System.Diagnostics.Debug.WriteLine(ex)
            End Try
        Loop While True
    End Sub

    Private Shared Sub AddPageToLoad(ByVal p As comicPage)
        Lock(_lockStack)
        _loadStack.Push(p)
        Unlock(_lockStack)
        ' Notify The System that something is on the Stack
        _loadImageEvent.Set()
    End Sub

    Private Shared Function ProcessLoadImageStack(ByVal o As Object) As Boolean
        ProcessLoadImageStack = False
        Dim p As comicPage = Nothing
        Lock(_lockStack)
        If _loadStack.Count > 0 Then p = _loadStack.Pop
        Unlock(_lockStack)
        If Not p Is Nothing Then
            p.DoLoadImage(Nothing)
            ProcessLoadImageStack = True
        End If
    End Function


    Public Property IsLoading() As Boolean
        Get
            Return _loading
        End Get
        Private Set(ByVal value As Boolean)
            _loading = value
            OnPropertyChanged("IsLoading")
        End Set
    End Property

    Public Property IsLargeImageLoading() As Boolean
        Get
            Return _largeImageLoading
        End Get
        Private Set(ByVal value As Boolean)
            _largeImageLoading = value
            OnPropertyChanged("IsLargeImageLoading")
        End Set
    End Property

    Public ReadOnly Property FileNameUri() As Uri
        Get
            Return New Uri(_filename)
        End Get
    End Property

    Public ReadOnly Property FileName() As String
        Get
            Return System.IO.Path.GetFileNameWithoutExtension(_filename)
        End Get
    End Property

    Public ReadOnly Property Height() As Double
        Get
            Return Me.LargeImageSource.Height
        End Get
    End Property
    Public ReadOnly Property Width() As Double
        Get
            Return Me.LargeImageSource.Width
        End Get
    End Property

    Public Property LargeImageSource() As ImageSource
        Get
            Try
                If _largeImageSource Is Nothing Then
                    Lock(_lockLargeLoad)
                    _largePageToLoad = Me
                    IsLargeImageLoading = True
                    _loadLargePhotoEvent.Set()
                    Unlock(_lockLargeLoad)
                    If Not _imageSource Is Nothing Then Return _imageSource
                    Return Nothing
                Else
                    ' this is a "one time" property internally...
                    Dim temp As ImageSource = _largeImageSource
                    _largeImageSource = Nothing
                    Return temp
                End If
            Catch ex As Exception
                System.Diagnostics.Debug.WriteLine(ex.ToString())
            End Try
            Return Nothing
        End Get
        Private Set(ByVal value As ImageSource)
            _largeImageSource = value
            IsLargeImageLoading = False
            OnPropertyChanged("LargeImageSource")
        End Set
    End Property

    Public Property ImageSource() As ImageSource
        Get
            If _imageSource Is Nothing Then
                LoadImage()
                Return _hourGlass
            End If
            Return _imageSource
        End Get
        Private Set(ByVal value As ImageSource)
            _imageSource = value
            OnPropertyChanged("ImageSource")
        End Set
    End Property



    Public Sub New(ByVal filename As String)
        _filename = filename
        OnPropertyChanged("IsLoading")

    End Sub

    Private Sub LoadImage()
        If _loading Then Return
        Me.IsLoading = True
        AddPageToLoad(Me)
    End Sub

    Private Sub LoadImageThreadWorker(ByVal mem As MemoryStream)
        Dim foundThumbnail As Boolean
        Dim imgDecoder As BitmapDecoder = BitmapDecoder.Create(mem, BitmapCreateOptions.None, BitmapCacheOption.None)
        Dim frame As BitmapFrame = imgDecoder.Frames(0)
        _imageSource = frame.Thumbnail
        foundThumbnail = IIf(Not _imageSource Is Nothing, True, False)

        If Not foundThumbnail Then

            ' we'll make a thumbnail image then ... (too bad as the pre-created one is FAST!)
            Dim thumbnail As New TransformedBitmap()
            thumbnail.BeginInit()
            thumbnail.Source = CType(frame, BitmapSource)

            ' we'll make a reasonable sized thumnbail with a height of 240
            Dim pixelH As Integer = frame.PixelHeight
            Dim pixelW As Integer = frame.PixelWidth
            Dim decodeH As Integer = 240
            Dim decodeW As Integer = ((frame.PixelWidth * decodeH) / pixelH)

            Dim scaleX As Double = decodeW / CDbl(pixelW)
            Dim scaleY As Double = decodeH / CDbl(pixelH)
            Dim TransformGroup As New TransformGroup()
            TransformGroup.Children.Add(New ScaleTransform(scaleX, scaleY))
            thumbnail.Transform = TransformGroup
            thumbnail.EndInit()

            ' this will disconnect the stream from the image completely ...
            Dim writable As New WriteableBitmap(thumbnail)
            writable.Freeze()
            _imageSource = writable
        End If
    End Sub

    Private Sub BackgroundLargeImageSourceLoad(ByVal o As Object)
        Dim bi As New BitmapImage(Me.FileNameUri)
        bi.Freeze()
        _largeImageSource = bi
        OnPropertyChanged("LargeImageSource")
    End Sub

    Protected Overridable Sub DoLoadImage(ByVal o As Object)
        ' If we don't need to load something, exit 
        If Not _loading Then Exit Sub
        If IO.File.Exists(_filename) Then
            Dim mem As MemoryStream = Nothing
            Try
                Dim buffer As Byte() = File.ReadAllBytes(_filename)
                ' By Reading the data into an in-memory buffer, we prvent the file from being read
                ' in the UI thread which speeds up access 
                mem = New MemoryStream(buffer)
                If Not _loading Then
                    mem.Dispose()
                    Exit Sub
                End If
                ' we need to read the metadata in the UI thread, so we'll open the bitmap and grab it using a 
                ' delegate/invoke. (since the user is waiting for this, there's little benefit to doing this
                ' as an async invoke).
                ' Dim noad As New NoArgumentDelegate(AddressOf LoadImageThreadWorker)
                ' Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, noad, mem)
                Dim callBack As New WaitCallback(AddressOf LoadImageThreadWorker)
                ThreadPool.QueueUserWorkItem(callBack, mem)
                'LoadImageThreadWorker(mem)
            Catch ex As Exception
                System.Diagnostics.Debug.WriteLine(ex)

            Finally
                If Not mem Is Nothing Then mem.Dispose()

                Me.IsLoading = False
                OnPropertyChanged("ImageSource")
                OnPropertyChanged("LargeImageSource")
            End Try
        End If
    End Sub

    Protected Overridable Sub CopyStream(ByVal inputStream As System.IO.Stream, ByVal outputStream As IO.Stream)
        Dim buffer As Byte()
        ReDim buffer(BufferSize)
        Dim byteCount As Integer = inputStream.Read(buffer, 0, BufferSize)
        While byteCount > 0
            outputStream.Write(buffer, 0, byteCount)
            byteCount = inputStream.Read(buffer, 0, BufferSize)
        End While
    End Sub

    Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Public Sub CleanUp()
        Me.IsLoading = False
        ImageSource = Nothing
    End Sub

    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
